﻿using DPSBase;
using NetworkCommsDotNet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using System.Windows.Threading;
using System.IO;
using System.IO.IsolatedStorage;
using System.Windows.Media.Imaging;
using System.Diagnostics;
using GalaSoft.MvvmLight.Messaging;
using JmShared;
using Microsoft.Phone.Net.NetworkInformation;

namespace JiveMessenger.Network
{
    public class NetworkConnection
    {
        private SendReceiveOptions _encryptedSendReceiveOptions;
        private SendReceiveOptions _unencryptedSendReceiveOptions;
        private string _clientVersion;
        private int _messageID;
        private string _deviceID;
        private Dictionary<int, Dictionary<int, ChatMessage>> _queuedMessages;
        private Dictionary<int, int> _processedMessageCount;
        private Dictionary<int, DateTime> _processedMessageTime;
        private Dictionary<int, List<ChatMessage>> _addedBatchMessages;
        private object ReceiveChatMessageLockObject = new object();
        public ConnectionInfo MasterConnectionInfo { get; set; }
        private string _currentUserID;

        public event EventHandler<EventArgs> OnDisconnected;
        public event EventHandler<MessageQueueUpdateEventArgs> MessageQueueItemReceived;
        public event EventHandler<ChatMessageEventArgs> ChatMessageReceived;
        public event EventHandler<ChatMessageEventArgs> ChatMessageSendt;
        public event EventHandler<ChatReceiptEventArgs> ChatReceiptReceived;
        public event EventHandler<HostnameLookupEventArgs> HostnameLookupSucceeded;
        public event EventHandler<ContactEmoticonEventArgs> ContactEmoticonReceived;
        public event EventHandler<ClientListEventArgs> ClientListReceived;
        public event EventHandler<UserSettingsEventArgs> UserSettingsReceived;
        public event EventHandler<ServerInfoMessageEventArgs> ServerInfoMessageReceived;
        public event EventHandler<DeviceStatusEventArgs> DeviceStatusReceived;

        public int RemainingMessages { get; set; }
        public NetworkConnection(string uniqueDeviceID, string clientVersion)
        {
            this._deviceID = uniqueDeviceID;
            this._clientVersion = clientVersion;
        }

        public DatabaseQueryResponse SendFriendRequest(ChatMessage msg)
        {
            var result = TCPConnection.GetConnection(MasterConnectionInfo).SendReceiveObject<DatabaseQueryResponse>("ChatMessage", "SendFriendRequestResponse", 6000, msg);
            if (result == null)
                result = new DatabaseQueryResponse(false, "Timeout contacting the server");
            return result;
        }

        public DatabaseQueryResponse SendCreateGroupRequest(ChatMessage msg)
        {
            var result = TCPConnection.GetConnection(MasterConnectionInfo).SendReceiveObject<DatabaseQueryResponse>("ChatMessage", "CreateChatGroupResponse", 6000, msg);
            if (result == null)
                result = new DatabaseQueryResponse(false, "Timeout contacting the server");
            return result;
        }
        public DatabaseQueryResponse SendRenameGroupRequest(ChatMessage msg)
        {
            var result = TCPConnection.GetConnection(MasterConnectionInfo).SendReceiveObject<DatabaseQueryResponse>("ChatMessage", "RenameChatGroupResponse", 6000, msg);
            if (result == null)
                result = new DatabaseQueryResponse(false, "Timeout contacting the server");
            return result;
        }
        public DatabaseQueryResponse SendGroupInvite(ChatMessage msg)
        {
            var result = TCPConnection.GetConnection(MasterConnectionInfo).SendReceiveObject<DatabaseQueryResponse>("ChatMessage", "SendGroupInviteResponse", 6000, msg);
            if (result == null)
                result = new DatabaseQueryResponse(false, "Timeout contacting the server");
            return result;
        }

        //private async Task<Tuple<bool, string>> SendFriendRequestAsync(ChatMessage msg)
        //{
        //    return TCPConnection.GetConnection(MasterConnectionInfo).SendReceiveObject<Tuple<bool, string>>("ChatMessage", "", 6000, msg);
        //}
        /// <summary>
        /// Sends the chatmessage to the server
        /// </summary>
        /// <param name="msg"></param>
        public bool SendMessage(ChatMessage msg)
        {
            if (MasterConnectionInfo == null)
                return false;
            if (MasterConnectionInfo.ConnectionState != ConnectionState.Established)
                return false;
            try
            {
                //  SendReceiveOptions options = _unencryptedSendReceiveOptions;
                if (msg.MessageType == MessageType.GroupMessage || msg.MessageType == MessageType.PrivateMessage)
                {
                    msg.MessageID = Interlocked.Increment(ref _messageID);
                    //  options = _encryptedSendReceiveOptions;
                }

                TCPConnection.GetConnection(MasterConnectionInfo).SendObject("ChatMessage", msg);
                if (ChatMessageSendt != null)
                    ChatMessageSendt(this, new ChatMessageEventArgs(msg));
            }
            catch (CommsException err)
            {
                Debug.WriteLine("Error sending message: {0}", err);
                return false;
            }
            return true;
        }
        public bool SendDeviceStatus(DeviceStatus msg)
        {
            if (MasterConnectionInfo == null)
                return false;
            if (MasterConnectionInfo.ConnectionState != ConnectionState.Established)
                return false;
            try
            {
                TCPConnection.GetConnection(MasterConnectionInfo).SendObject("DeviceStatus", msg);
            }
            catch (CommsException err)
            {
                Debug.WriteLine("Error sending device status: {0}", err);
                return false;
            }
            return true;
        }
        public bool SendLoginInfo(LoginInfo info)
        {
            if (MasterConnectionInfo == null)
                return false;
            if (MasterConnectionInfo.ConnectionState != ConnectionState.Established)
                return false;
            try
            {
                TCPConnection.GetConnection(MasterConnectionInfo).SendObject("LoginInfo", info, _encryptedSendReceiveOptions);
            }
            catch (CommsException err)
            {
                Debug.WriteLine("Error sending message: {0}", err);
                return false;
            }
            return true;
        }
        public bool SendEmoticon(ContactEmoticon emoticon)
        {
            if (MasterConnectionInfo == null)
                return false;
            if (MasterConnectionInfo.ConnectionState != ConnectionState.Established)
                return false;
            try
            {
                TCPConnection.GetConnection(MasterConnectionInfo).SendObject("ContactEmoticon", emoticon, _unencryptedSendReceiveOptions);
            }
            catch (CommsException err)
            {
                Debug.WriteLine("Error sending message: {0}", err);
                return false;
            }
            return true;
        }

        public bool SendUserSettings(WpUserSettings settings)
        {
            if (MasterConnectionInfo == null)
                return false;
            if (MasterConnectionInfo.ConnectionState != ConnectionState.Established)
                return false;
            try
            {
                TCPConnection.GetConnection(MasterConnectionInfo).SendObject("WpUserSettings", settings);
            }
            catch (CommsException err)
            {
                Debug.WriteLine("Error sending message: {0}", err);
                return false;
            }
            return true;
        }

        public void SendClientStatus(ClientStatus? clientStatus, UserInfo sender, string message = "")
        {
            //ChatMessage msg = new ChatMessage { SenderID = _currentUserID, SenderInfo = sender, MessageType = mt, Message = message };
            DeviceStatus status = new DeviceStatus(_currentUserID, clientStatus);
            SendDeviceStatus(status);
        }

        public void SendResetMessageCountMessage()
        {
            SendMessage(new ChatMessage(this._currentUserID, MessageType.ResetUnreadMessageCount));
        }
        public void SendPushUri(string pushUri)
        {
            SendMessage(new ChatMessage(this._currentUserID, MessageType.SendPushUri, pushUri));
        }

        /// <summary>
        /// Disconnects from the server
        /// </summary>
        /// <returns></returns>
        public void Logout()
        {
            SendMessage(new ChatMessage(this._currentUserID, MessageType.Logout));
            TCPConnection.GetConnection(MasterConnectionInfo).ConnectionAlive(5);
        }

        public bool Login(string server, string username, string password, int port)
        {
            return Connect(server, username, password, port);
        }

        /// <summary>
        /// Connects to the server
        /// </summary>
        /// <returns></returns>
        private bool Connect(string server, string username, string password, int port)
        {
            try
            {
                if (MasterConnectionInfo != null)
                {
                    if (MasterConnectionInfo.ConnectionState == ConnectionState.Established)
                        TCPConnection.GetConnection(MasterConnectionInfo).CloseConnection(false);

                    MasterConnectionInfo = null;
                }

                if (String.IsNullOrEmpty(server))
                {
                    Messenger.Default.Send("No servers configured");
                    if (OnDisconnected != null)
                        OnDisconnected(this, null);
                    return false;
                }
                IPEndPoint[] serverAddresses = { };
                bool done = false;

                DeviceNetworkInformation.ResolveHostNameAsync(new DnsEndPoint(server, port), networkInfo =>
                {
                    try
                    {
                        var add = networkInfo.IPEndPoints;
                        serverAddresses = add;
                    }
                    catch (Exception err)
                    {
                        Messenger.Default.Send("Exception in hostname lookup : " + err.Message);
                        if (OnDisconnected != null)
                            OnDisconnected(this, null);
                    }
                    finally
                    {
                        done = true;
                    }

                }, null);
                while (!done)
                    Thread.Sleep(5);

                string connectionIP = string.Empty;
#if DEBUG
                connectionIP = "192.168.1.59";
#else

                if (serverAddresses == null || serverAddresses.Length == 0)
                {
                    if (Settings.HostnameCache != null)
                    {
                        if (Settings.HostnameCache.ContainsKey(Settings.ServerSetting))
                        {
                            connectionIP = Settings.HostnameCache[Settings.ServerSetting];
                        }
                        else if (Settings.HostnameCache.ContainsKey(Settings.ServerSetting.ToLower()))
                        {
                            connectionIP = Settings.HostnameCache[Settings.ServerSetting.ToLower()];
                        }
                        else
                            throw new NetworkException(NetworkError.NameResolutionNoHostAddresses);
                    }
                    else
                        throw new NetworkException(NetworkError.NameResolutionNoHostAddresses);
                }
                else
                    connectionIP = serverAddresses[0].Address.ToString();

#endif

                MasterConnectionInfo = new ConnectionInfo(connectionIP, port);
                var dataProcessors = new List<DataProcessor>();
                dataProcessors.Add(DPSManager.GetDataProcessor<RijndaelPSKEncrypter>());

                var dataProcessorOptions = new Dictionary<string, string>();
                RijndaelPSKEncrypter.AddPasswordToOptions(dataProcessorOptions, "fg342ssdfs43232fdfgdg3452fdsgbvcvcblkiuyoaxc");

                DataSerializer dataSerializer = DPSManager.GetDataSerializer<ProtobufSerializer>();
                _encryptedSendReceiveOptions = new SendReceiveOptions(dataSerializer, dataProcessors, dataProcessorOptions);
                _unencryptedSendReceiveOptions = new SendReceiveOptions(dataSerializer, new List<DataProcessor>(), new Dictionary<string, string>());
                NetworkComms.DefaultSendReceiveOptions = _encryptedSendReceiveOptions;

                NetworkComms.ConnectionEstablishTimeoutMS = 2000;
                NetworkComms.ConnectionAliveTestTimeoutMS = 2000;
                NetworkComms.AppendGlobalIncomingPacketHandler<ChatMessage>("ChatMessage", ProcessMessage, _encryptedSendReceiveOptions);
                NetworkComms.AppendGlobalIncomingPacketHandler<ContactEmoticon>("ContactEmoticon", ProcessEmoticon);
                NetworkComms.AppendGlobalIncomingPacketHandler<ChatReceipt>("Receipt", ProcessReceipt);
                NetworkComms.AppendGlobalIncomingPacketHandler<ClientList>("ClientList", ProcessClientList);
                NetworkComms.AppendGlobalIncomingPacketHandler<WpUserSettings>("WpUserSettings", ProcessUserSettings);
                NetworkComms.AppendGlobalIncomingPacketHandler<ServerInfoMessage>("ServerInfoMessage", ProcessServerInfoMessage);
                NetworkComms.AppendGlobalIncomingPacketHandler<DeviceStatus>("DeviceStatus", ProcessDeviceStatus);

                NetworkComms.AppendGlobalConnectionCloseHandler(client_OnDisconnected);
                NetworkComms.IgnoreUnknownPacketTypes = true;
                NetworkComms.EnablePacketCheckSumValidation = true;

                TCPConnection.GetConnection(MasterConnectionInfo).EstablishConnection(); //Connect

                if (MasterConnectionInfo.ConnectionState != ConnectionState.Established)
                {
                    Messenger.Default.Send("Connection failed");
                    return false;
                }
                if (HostnameLookupSucceeded != null && serverAddresses != null && serverAddresses.Length > 0)
                    HostnameLookupSucceeded(this, new HostnameLookupEventArgs(serverAddresses[0].Address.ToString()));
                UserInfo user = new UserInfo(username, _deviceID)
                {
                    ClientStatus = ClientStatus.Available,
                };
                _currentUserID = user.UserID;
                //ChatMessage msg = new ChatMessage
                //{
                //    MessageType = MessageType.Login,
                // Message = StaticMethods.ObjectToString<LoginInfo>(new LoginInfo
                LoginInfo info = new LoginInfo(username, password, _clientVersion, user);

                //    },
                //    SenderID = user.UserID
                //};
                SendLoginInfo(info);
                //SendMessage(msg);

                _queuedMessages = new Dictionary<int, Dictionary<int, ChatMessage>>();
                _processedMessageTime = new Dictionary<int, DateTime>();
                _addedBatchMessages = new Dictionary<int, List<ChatMessage>>();
                _processedMessageCount = new Dictionary<int, int>();

                return true;
            }
            catch
            {
                throw;

            }
        }
        private void ProcessDeviceStatus(PacketHeader packetHeader, Connection connection, DeviceStatus incomingObject)
        {
            if (DeviceStatusReceived != null)
                DeviceStatusReceived(this, new DeviceStatusEventArgs(connection, incomingObject));
        }
        private void ProcessServerInfoMessage(PacketHeader packetHeader, Connection connection, ServerInfoMessage incomingObject)
        {
            if (ServerInfoMessageReceived != null)
                ServerInfoMessageReceived(this, new ServerInfoMessageEventArgs(incomingObject));
        }

        private void ProcessUserSettings(PacketHeader packetHeader, Connection connection, WpUserSettings incomingObject)
        {
            if (UserSettingsReceived != null)
                UserSettingsReceived(this, new UserSettingsEventArgs(incomingObject));
        }

        private void ProcessClientList(PacketHeader packetHeader, Connection connection, ClientList clients)
        {
            if (ClientListReceived != null)
                ClientListReceived(this, new ClientListEventArgs(clients));
        }

        private void ProcessEmoticon(PacketHeader packetHeader, Connection connection, ContactEmoticon emoticon)
        {
            if (ContactEmoticonReceived != null)
                ContactEmoticonReceived(this, new ContactEmoticonEventArgs(emoticon));
        }

        private void ProcessReceipt(PacketHeader packetheader, Connection connection, ChatReceipt receipt)
        {
            if (ChatReceiptReceived != null)
                ChatReceiptReceived(this, new ChatReceiptEventArgs(receipt));
        }
        /// <summary>
        /// Process received ChatMessage objects
        /// </summary>
        /// <param name="header"></param>
        /// <param name="connection"></param>
        /// <param name="msg"></param>
        private void ProcessMessage(PacketHeader header, Connection connection, ChatMessage msg)
        {

            if (msg.MessageQueueInfo != null)
            {
                lock (this.ReceiveChatMessageLockObject)
                {
                    int queuedBatchNumber = msg.MessageQueueInfo.QueuedBatchNumber;
                    int queuedMessageCount = msg.MessageQueueInfo.QueuedMessageCount;
                    int queuedMessageNumber = msg.MessageQueueInfo.QueuedMessageNumber;

                    if (!this._processedMessageCount.ContainsKey(queuedBatchNumber))
                        this._processedMessageCount[queuedBatchNumber] = 0;

                    if (!_queuedMessages.ContainsKey(queuedBatchNumber))
                        _queuedMessages[queuedBatchNumber] = new Dictionary<int, ChatMessage>();

                    if (!_addedBatchMessages.ContainsKey(queuedBatchNumber))
                        _addedBatchMessages[queuedBatchNumber] = new List<ChatMessage>();

                    int lastProcessedNum = _addedBatchMessages[queuedBatchNumber].Count > 0 ? _addedBatchMessages[queuedBatchNumber].Last().MessageQueueInfo.QueuedMessageNumber : 0;
                    _processedMessageCount[queuedBatchNumber]++;
                    _processedMessageTime[queuedBatchNumber] = DateTime.Now;

                    RemainingMessages = queuedMessageCount - _processedMessageCount[queuedBatchNumber];
                    if (MessageQueueItemReceived != null)
                        MessageQueueItemReceived(this, new MessageQueueUpdateEventArgs(_processedMessageCount[queuedBatchNumber], queuedMessageCount, queuedBatchNumber));

                    if (queuedMessageNumber > lastProcessedNum + 1 && queuedMessageNumber > 1)
                    {
                        _queuedMessages[queuedBatchNumber][queuedMessageNumber] = msg;

                        while (true)
                        {
                            int i = 1;
                            if (_queuedMessages[queuedBatchNumber].ContainsKey(lastProcessedNum + i))
                            {
                                HandleQueuedMessage(_queuedMessages[queuedBatchNumber][lastProcessedNum + i]);
                                System.Diagnostics.Debug.WriteLine(string.Format("handling previously queued message number: {0} for batch number {1}", (lastProcessedNum + i), queuedBatchNumber));
                                i++;
                            }
                            else
                                break;
                        }
                    }
                    else
                    {
                        HandleQueuedMessage(msg);
                        System.Diagnostics.Debug.WriteLine(string.Format("handling message number: {0} for batch number {1}", (lastProcessedNum + 1), queuedBatchNumber));
                    }

                    if (_processedMessageCount[queuedBatchNumber] == queuedMessageCount)
                    {
                        List<ChatMessage> messages = _queuedMessages[queuedBatchNumber].Values.OrderBy(x => queuedMessageNumber).ToList();
                        foreach (ChatMessage m in messages)
                            HandleQueuedMessage(m);

                        this._queuedMessages.Remove(queuedBatchNumber);
                        this._processedMessageCount.Remove(queuedBatchNumber);
                        _processedMessageTime.Remove(queuedBatchNumber);
                    }
                }
            }
            else
                ProcessChatMessage(msg); //1 item in the queue, so we skip the queue system and just process it.
        }

        private void HandleQueuedMessage(ChatMessage msg)
        {
            ProcessChatMessage(msg);
            _addedBatchMessages[msg.MessageQueueInfo.QueuedBatchNumber].Add(msg);
            _queuedMessages[msg.MessageQueueInfo.QueuedBatchNumber].Remove(msg.MessageQueueInfo.QueuedMessageNumber);
        }
        public void ProcessChatMessage(ChatMessage msg)
        {
            if (ChatMessageReceived != null)
                ChatMessageReceived(this, new ChatMessageEventArgs(msg));
        }

        /// <summary>
        /// Triggers on disconnect from the server.
        /// </summary>
        private void client_OnDisconnected(Connection connection)
        {

            _currentUserID = string.Empty;

            NetworkComms.RemoveGlobalConnectionCloseHandler(client_OnDisconnected);
            NetworkComms.RemoveGlobalIncomingPacketHandler("ChatMessage");
            NetworkComms.RemoveGlobalIncomingPacketHandler("ContactEmoticon");
            NetworkComms.RemoveGlobalIncomingPacketHandler("Receipt");
            NetworkComms.RemoveGlobalIncomingPacketHandler("ClientList");
            NetworkComms.RemoveGlobalIncomingPacketHandler("PcUserSettings");
            NetworkComms.RemoveGlobalIncomingPacketHandler("ServerInfoMessage");
            NetworkComms.RemoveGlobalIncomingPacketHandler("DeviceStatus");


            if (OnDisconnected != null)
                OnDisconnected(this, null);
        }
    }

    public class ChatMessageEventArgs : EventArgs
    {
        public ChatMessage Message { get; set; }
        public ChatMessageEventArgs(ChatMessage msg)
        {
            this.Message = msg;
        }
    }
    public class DeviceStatusEventArgs : EventArgs
    {
        public Connection Connection { get; set; }
        public DeviceStatus Status { get; set; }
        public DeviceStatusEventArgs(Connection connection, DeviceStatus status)
        {
            this.Status = status;
            this.Connection = connection;
        }
    }
    public class ServerInfoMessageEventArgs : EventArgs
    {
        public ServerInfoMessage Message { get; set; }
        public ServerInfoMessageEventArgs(ServerInfoMessage msg)
        {
            this.Message = msg;
        }
    }
    public class UserSettingsEventArgs : EventArgs
    {
        public WpUserSettings Settings { get; set; }
        public UserSettingsEventArgs(WpUserSettings settings)
        {
            this.Settings = settings;
        }
    }
    public class ClientListEventArgs : EventArgs
    {
        public ClientList ClientList { get; set; }
        public ClientListEventArgs(ClientList clients)
        {
            this.ClientList = clients;
        }
    }
    public class ContactEmoticonEventArgs : EventArgs
    {
        public ContactEmoticon Emoticon { get; set; }
        public ContactEmoticonEventArgs(ContactEmoticon emoticon)
        {
            this.Emoticon = emoticon;
        }
    }
    public class ChatReceiptEventArgs : EventArgs
    {
        public ChatReceipt Receipt { get; set; }
        public ChatReceiptEventArgs(ChatReceipt receipt)
        {
            this.Receipt = receipt; ;
        }
    }

    public class HostnameLookupEventArgs : EventArgs
    {
        public string IPAddress { get; private set; }
        public HostnameLookupEventArgs(string ip)
        {
            this.IPAddress = ip;
        }
    }

    public class MessageQueueUpdateEventArgs : EventArgs
    {
        public int ReceivedMessages { get; set; }
        public int TotalMessagesInQueue { get; set; }
        public int BatchNumber { get; set; }
        public MessageQueueUpdateEventArgs(int receivedMessages, int totalMessages, int batchNumber)
        {
            this.ReceivedMessages = receivedMessages;
            this.TotalMessagesInQueue = totalMessages;
            this.BatchNumber = batchNumber;
        }
    }

}
